/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel
 * FILE:            ntoskrnl/include/internal/i386/asmmacro.S
 * PURPOSE:         Assembly Macros for Spinlocks and common Trap Code
 * PROGRAMMERS:     Alex Ionescu (alex@relsoft.net)
 *                  Timo Kreuzer (timo.kreuzer@reactos.org)
 */

// Arguments for idt
#define INT_32_DPL0                 HEX(08E00)
#define INT_32_DPL3                 HEX(0EE00)

//
// These macros are inlined equivalents of KiAcquire/ReleaseSpinlock, that is,
// they will not be compiled into non-SMP builds. Usage is as follows:
//
// .BeginYourFunction
//      mov reg, lockaddr
//      ACQUIRE_SPINLOCK(reg, .spin)
//      <thread-safe code here>
//      RELEASE_SPINLOCK(reg)
//      <misc code here>
//  retn
//  #IFDEF CONFIG_SMP
//  .spin
//      <any necessary steps to be able to jump back safely>
//       SPIN_ON_LOCK(reg, .BeginYourFunction)
//  #ENDIF
//
#ifdef CONFIG_SMP
#define LOCK lock
#define ACQUIRE_SPINLOCK(x, y) \
    lock bts dword ptr [x], 0; \
    jb y
#define RELEASE_SPINLOCK(x) mov byte ptr [x], 0
#define SPIN_ON_LOCK(x, y) \
1: \
    test dword ptr [x], 1; \
    jz y; \
    pause; \
    jmp 1b
#else
#define LOCK
#define ACQUIRE_SPINLOCK(x, y)
#define RELEASE_SPINLOCK(x)
#endif

//
// @name IDT
//
// This macro creates an IDT entry for the given handler
//
// @param Handler
//        Pointer to the IDT handler
//
// @param Bits
//        Descriptor Bits to associate
//
// @remark None.
//
MACRO(idt, Handler, Bits)
    .long VAL(Handler)
    .short VAL(Bits)
    .short KGDT_R0_CODE
ENDM


#define KI_PUSH_FAKE_ERROR_CODE HEX(0001)
#define KI_UNUSED               HEX(0002)
#define KI_NONVOLATILES_ONLY    HEX(0004)
#define KI_FAST_SYSTEM_CALL     HEX(0008)
#define KI_SOFTWARE_TRAP        HEX(0010)
#define KI_HARDWARE_INT         HEX(0020)
#define KI_DONT_SAVE_SEGS       HEX(0100)

MACRO(KiEnterTrap, Flags)
    LOCAL not_v86_trap
    LOCAL set_sane_segs

    /* Check what kind of trap frame this trap requires */
    if (Flags AND KI_FAST_SYSTEM_CALL)

        /* SYSENTER requires us to build a complete ring transition trap frame */
        FrameSize = KTRAP_FRAME_EIP

        /* Fixup fs. cx is free to clobber */
        mov cx, KGDT_R0_PCR
        mov fs, cx

        /* Get pointer to the TSS */
        mov ecx, fs:[KPCR_TSS]

        /* Get a stack pointer */
        mov esp, [ecx + KTSS_ESP0]

        /* Set up a fake hardware trap frame */
        push KGDT_R3_DATA or RPL_MASK
        push edx
        pushfd
        push KGDT_R3_CODE or RPL_MASK
        push dword ptr ds:[KUSER_SHARED_SYSCALL_RET]

    elseif (Flags AND KI_SOFTWARE_TRAP)

        /* Software traps need a complete non-ring transition trap frame */
        FrameSize = KTRAP_FRAME_ESP

        /* Software traps need to get their EIP from the caller's frame */
        pop eax

    elseif (Flags AND KI_PUSH_FAKE_ERROR_CODE)

        /* If the trap doesn't have an error code, we'll make space for it */
        FrameSize = KTRAP_FRAME_EIP

    else

        /* The trap already has an error code, so just make space for the rest */
        FrameSize = KTRAP_FRAME_ERROR_CODE

    endif

    /* Make space for this frame */
    sub esp, FrameSize

    /* Save nonvolatile registers */
    mov [esp + KTRAP_FRAME_EBP], ebp
    mov [esp + KTRAP_FRAME_EBX], ebx
    mov [esp + KTRAP_FRAME_ESI], esi
    mov [esp + KTRAP_FRAME_EDI], edi

    /* Save eax for system calls, for use by the C handler */
    mov [esp + KTRAP_FRAME_EAX], eax

    /* Does the caller want nonvolatiles only? */
    if (NOT (Flags AND KI_NONVOLATILES_ONLY))
        /* Otherwise, save the volatiles as well */
        mov [esp + KTRAP_FRAME_ECX], ecx
        mov [esp + KTRAP_FRAME_EDX], edx
    endif

    /* Save segment registers? */
    if (Flags AND KI_DONT_SAVE_SEGS)

        /* Initialize TrapFrame segment registers with sane values */
        mov eax, KGDT_R3_DATA OR RPL_MASK
        mov ecx, fs
        mov [esp + KTRAP_FRAME_DS], eax
        mov [esp + KTRAP_FRAME_ES], eax
        mov [esp + KTRAP_FRAME_FS], ecx
        mov dword ptr [esp + KTRAP_FRAME_GS], 0

    else

        /* Check for V86 mode */
        test byte ptr [esp + KTRAP_FRAME_EFLAGS + 2], (EFLAGS_V86_MASK / HEX(10000))
        jz not_v86_trap

            /* Restore V8086 segments into Protected Mode segments */
            mov eax, [esp + KTRAP_FRAME_V86_DS]
            mov ecx, [esp + KTRAP_FRAME_V86_ES]
            mov [esp + KTRAP_FRAME_DS], eax
            mov [esp + KTRAP_FRAME_ES], ecx
            mov eax, [esp + KTRAP_FRAME_V86_FS]
            mov ecx, [esp + KTRAP_FRAME_V86_GS]
            mov [esp + KTRAP_FRAME_FS], eax
            mov [esp + KTRAP_FRAME_GS], ecx
            jmp set_sane_segs

        not_v86_trap:

            /* Save segment selectors */
            mov eax, ds
            mov ecx, es
            mov [esp + KTRAP_FRAME_DS], eax
            mov [esp + KTRAP_FRAME_ES], ecx
            mov eax, fs
            mov ecx, gs
            mov [esp + KTRAP_FRAME_FS], eax
            mov [esp + KTRAP_FRAME_GS], ecx

    endif

set_sane_segs:
    /* Load correct data segments */
    mov ax, KGDT_R3_DATA OR RPL_MASK
    mov ds, ax
    mov es, ax

    /* Fast system calls have fs already fixed */
    if (Flags AND KI_FAST_SYSTEM_CALL)

        /* Enable interrupts and set a sane FS value */
        or dword ptr [esp + KTRAP_FRAME_EFLAGS], EFLAGS_INTERRUPT_MASK
        mov dword ptr [esp + KTRAP_FRAME_FS], KGDT_R3_TEB or RPL_MASK

        /* Set sane active EFLAGS */
        push 2
        popfd

        /* Point edx to the usermode parameters */
        add edx, 8
    else
        /* Otherwise fix fs now */
        mov ax, KGDT_R0_PCR
        mov fs, ax
    endif

#if DBG
    /* Keep the frame chain intact */
    mov eax, [esp + KTRAP_FRAME_EIP]
    mov [esp + KTRAP_FRAME_DEBUGEIP], eax
    mov [esp + KTRAP_FRAME_DEBUGEBP], ebp
    mov ebp, esp
#endif

    /* Set parameter 1 (ECX) to point to the frame */
    mov ecx, esp

    /* Clear direction flag */
    cld

ENDM

MACRO(KiCallHandler, Handler)
#if DBG
    /* Use a call to get the return address for back traces */
    call Handler
#else
    /* Use the faster jmp */
    jmp Handler
#endif
    nop
ENDM

MACRO(TRAP_ENTRY, Trap, Flags)
    EXTERN @&Trap&Handler@4 :PROC
    PUBLIC _&Trap
    .PROC _&Trap
        /* Generate proper debugging symbols */
        FPO 0, 0, 0, 0, 1, FRAME_TRAP

        /* Common code to create the trap frame */
        KiEnterTrap Flags

        /* Call the C handler */
        KiCallHandler @&Trap&Handler@4
    .ENDP
ENDM

#define KI_NMI  HEX(0001)

MACRO(TASK_ENTRY, Trap, Flags)
    EXTERN _&Trap&Handler :PROC
    PUBLIC _&Trap
    .PROC _&Trap
        /* Generate proper debugging symbols */
        FPO 0, 0, 0, 0, 0, FRAME_TSS

        /* Call the C handler */
        call _&Trap&Handler

        if (Flags AND KI_NMI)
            /* Return from NMI: return with iret and handle NMI recursion */
            iretd
            jmp _&Trap
        endif

    .ENDP
ENDM

#define KI_RESTORE_EAX        HEX(0001)
#define KI_RESTORE_ECX_EDX    HEX(0002)
#define KI_RESTORE_FS         HEX(0004)
#define KI_RESTORE_SEGMENTS   HEX(0008)
#define KI_RESTORE_EFLAGS     HEX(0010)
#define KI_EXIT_SYSCALL       HEX(0020)
#define KI_EXIT_JMP           HEX(0040)
#define KI_EXIT_RET           HEX(0080)
#define KI_EXIT_IRET          HEX(0100)
#define KI_EDITED_FRAME       HEX(0200)
#define KI_EXIT_RET8          HEX(0400)
#define KI_RESTORE_VOLATILES  (KI_RESTORE_EAX OR KI_RESTORE_ECX_EDX)

MACRO(KiTrapExitStub, Name, Flags)
    LOCAL ret8_instruction
    LOCAL not_nested_int

PUBLIC @&Name&@4
@&Name&@4:

    if (Flags AND KI_EXIT_RET8) OR (Flags AND KI_EXIT_IRET)

        /* This is the IRET frame */
        OffsetEsp = KTRAP_FRAME_EIP

    elseif (Flags AND KI_RESTORE_EFLAGS)

        /* We will pop EFlags off the stack */
        OffsetEsp = KTRAP_FRAME_EFLAGS

    else

        OffsetEsp = 0

    endif

    if (Flags AND KI_EDITED_FRAME)

        /* Load the requested ESP */
        mov esp, [ecx + KTRAP_FRAME_TEMPESP]

        /* Put return address on the new stack */
        push [ecx + KTRAP_FRAME_EIP]

        /* Put EFLAGS on the new stack */
        push [ecx + KTRAP_FRAME_EFLAGS]

    else

        /* Point esp to an appropriate member of the frame */
        lea esp, [ecx + OffsetEsp]

    endif

    /* Restore non volatiles */
    mov ebx, [ecx + KTRAP_FRAME_EBX]
    mov esi, [ecx + KTRAP_FRAME_ESI]
    mov edi, [ecx + KTRAP_FRAME_EDI]
    mov ebp, [ecx + KTRAP_FRAME_EBP]

    if (Flags AND KI_RESTORE_EAX)

        /* Restore eax */
        mov eax, [ecx + KTRAP_FRAME_EAX]

    endif

    if (Flags AND KI_RESTORE_ECX_EDX)

        /* Restore volatiles */
        mov edx, [ecx + KTRAP_FRAME_EDX]
        mov ecx, [ecx + KTRAP_FRAME_ECX]

    elseif (Flags AND KI_EXIT_JMP)

        /* Load return address into edx */
        mov edx, [esp - OffsetEsp + KTRAP_FRAME_EIP]

    elseif (Flags AND KI_EXIT_SYSCALL)

        /* Set sysexit parameters */
        mov edx, [esp - OffsetEsp + KTRAP_FRAME_EIP]
        mov ecx, [esp - OffsetEsp + KTRAP_FRAME_ESP]

        /* Keep interrupts disabled until the sti / sysexit */
        and byte ptr [esp - OffsetEsp + KTRAP_FRAME_EFLAGS + 1], NOT (EFLAGS_INTERRUPT_MASK / HEX(100))

    endif

    if (Flags AND KI_RESTORE_SEGMENTS)

        /* Restore segments for user mode */
        mov ds, [esp - OffsetEsp + KTRAP_FRAME_DS]
        mov es, [esp - OffsetEsp + KTRAP_FRAME_ES]
        mov gs, [esp - OffsetEsp + KTRAP_FRAME_GS]

    endif

    if ((Flags AND KI_RESTORE_FS) OR (Flags AND KI_RESTORE_SEGMENTS))

        /* Restore user mode FS */
        mov fs, [esp - OffsetEsp + KTRAP_FRAME_FS]

    endif

    if (Flags AND KI_RESTORE_EFLAGS)

        if (Flags AND KI_EXIT_RET8)

            /* Check if we return from a nested interrupt, i.e. an interrupt
               that occurred in the ret8 return path between restoring
               EFLAGS and returning with the ret instruction. */
            cmp dword ptr [esp], offset ret8_instruction
            jne not_nested_int

            /* This is a nested interrupt, so we have 2 IRET frames.
               Skip the first, and go directly to the previous return address.
               Do not pass Go. Do not collect $200 */
            add esp, 12

not_nested_int:
            /* We are at the IRET frame, so push EFLAGS first */
            push dword ptr [esp + 8]

        endif

        /* Restore EFLAGS */
        popfd

    endif

    if (Flags AND KI_EXIT_SYSCALL)

        /* Enable interrupts and return to user mode.
           Both must follow directly after another to be "atomic". */
        sti
        sysexit

    elseif (Flags AND KI_EXIT_IRET)

        /* Return with iret */
        iretd

    elseif (Flags AND KI_EXIT_JMP)

        /* Return to kernel mode with a jmp */
        jmp edx

    elseif (Flags AND KI_EXIT_RET8)

        /* Return to kernel mode with a ret 8 */
ret8_instruction:
        ret 8

    elseif (Flags AND KI_EXIT_RET)

        /* Return to kernel mode with a ret */
        ret

    endif

ENDM

